#include "compedit.h"
#include "winassert.h"


namespace win
{
	using std::runtime_error;

	int s_labelWidth = 0;
	const int GUTTER = 4;
	inline int middle(int width)
	{
		const int v1 = width/2;
		const int v2 = s_labelWidth+GUTTER;
		return (v1 > v2) ? v2 : v1;
	}

	LRESULT CALLBACK CompositeEditBox::WndProc(HWND hwnd, UINT msg,
		WPARAM wParam, LPARAM lParam)
	{
		static Window w;
		static CompositeEditBox * box = 0;
		w.Attach(hwnd);
		switch (msg)                  
		{
		case WM_PAINT:
			box = static_cast <CompositeEditBox *> (w.GetPointer());
			if (box)
				box->OnPaint();
			break;
		case WM_SIZE:
			box = static_cast <CompositeEditBox *> (w.GetPointer());
			if (box)
				box->OnSize(LOWORD(lParam),HIWORD(lParam));
			break;
		case WM_COMMAND:
			{
				switch (HIWORD(wParam))
				{
				case EN_SETFOCUS:
					box = static_cast <CompositeEditBox *> (w.GetPointer());
					box->OnFocus((HWND) lParam);
					return TRUE;
				}
				break;
			}    
		case WM_VSCROLL:
			box = static_cast <CompositeEditBox *> (w.GetPointer());
			switch (LOWORD (wParam))
			{
			case SB_LINEUP:
				box->OnScroll(-1);
				break;
			case SB_LINEDOWN:
				box->OnScroll(1);
				break;
			case SB_PAGEUP:
				box->OnScrollPage(-1);
				break;
			case SB_PAGEDOWN:
				box->OnScrollPage(1);
				break;
			case SB_THUMBPOSITION:
				box->OnScrollPos(LOWORD(lParam));
				break;         
			}
		default:
			return DefDlgProc( hwnd,msg,wParam,lParam );
		}
		return DefDlgProc( hwnd,msg,wParam,lParam );
	}

	CompositeEditBox::Item::Item(const string& label, 
		TextControl * pCtl,
		const string& hint)
	{
		_label = label;
		_pCtl = pCtl;
		_hint = hint;
	};

	CompositeEditBox::Item::~Item()
	{
		_pCtl->Destroy();
		delete _pCtl;
	}

	CompositeEditBox::CompositeEditBox()
	{
		_itemHeight = _displayHeight = _itemWidth;
		_yPos = -1;
		_controlFont = NULL;
	}

	void CompositeEditBox::SetControlFont(HFONT font)
	{
		_controlFont = font;
		AdjustItemDimensions();
	}


	bool CompositeEditBox::HasItems()
	{
		return _yPos > -1;
	} 

	void CompositeEditBox::Create(Window &parent,const int child_index)
	{
		_child_index = child_index;
		const HWND parent_handle = parent.GetHandle();
		const HINSTANCE instance_handle = reinterpret_cast<HINSTANCE>
			(GetWindowLong(parent_handle,GWL_HINSTANCE));
		static bool classRegistered = false;
		static const char * CLASS = "composite_edit";
		if (!classRegistered) 
		{
			WNDCLASS wc;
			ZeroMemory(&wc,sizeof(wc));
			wc.style = CS_HREDRAW|CS_VREDRAW;
			wc.hInstance = instance_handle;
			wc.hbrBackground = reinterpret_cast <HBRUSH> (NULL);
			wc.hCursor = LoadCursor (NULL, IDC_ARROW);
			wc.lpfnWndProc = CompositeEditBox::WndProc;
			wc.lpszClassName = CLASS;
			wc.cbWndExtra = DLGWINDOWEXTRA;
			if (!RegisterClass(&wc))
				throw runtime_error("Register class failed");
		}
		_hwnd = CreateWindow(CLASS,NULL,
			WS_CHILD | WS_VISIBLE | WS_CLIPSIBLINGS | DS_3DLOOK		,
			0,0,0,0,
			parent_handle,
			reinterpret_cast<HMENU> (_child_index),
			instance_handle,
			static_cast<LPVOID> (this));
		if (_hwnd == NULL)
			throw runtime_error("Create window failed");
		SetPointer(this);   
		AdjustItemDimensions();
	}

	void CompositeEditBox::AdjustItemDimensions()
	{
		// compute minimum height
		TEXTMETRIC tm;
		HDC hdc = GetDC(_hwnd);
		HFONT hOldFont = (HFONT) SelectObject(hdc,_controlFont);
		GetTextMetrics(hdc,&tm);
		SelectObject(hdc,hOldFont);
		ReleaseDC(_hwnd,hdc);

		_itemHeight = MulDiv(12,tm.tmHeight,8) + 4;
		s_labelWidth = tm.tmAveCharWidth * 25;

	}	

	void CompositeEditBox::AdjustControlLayout()
	{
		const int ctlX = middle(_itemWidth);
		const int ctlWidth = _itemWidth - ctlX - GUTTER;
		int ctlTop = 0;
		HWND curr;
		HWND prev = HWND_BOTTOM;
		const int count = _items.size();
		if (count == 0)
		{
			_yPos = -1;
			SetScrollVert(0,0);
		}
		else
		{
			_yPos = 0;  
			_ySize = 0;      
			TextControl::ResetFontHeight();
			for (int i=0; i < count; i++)
			{
				TextControl * pWin = _items[i]->Ctl();
				curr = pWin->GetHandle();   
				SetWindowPos(curr,prev,ctlX,ctlTop+2,ctlWidth,
					_itemHeight*pWin->NumLines()-4,
					SWP_SHOWWINDOW);
				SetFontHandle(_controlFont);			
				prev = curr;
				ctlTop += _itemHeight;                
				if (ctlTop < _displayHeight)
					_ySize = i+1;   
			}
			SetScrollVert(count-_ySize,_yPos);
		}
	}

	void CompositeEditBox::SetItems(const ItemPtrBasket &s)
	{
		if (!_hwnd)
			throw runtime_error("Create window before setting items");
		LockWindowUpdate(_hwnd);    
		ClearItems(false);
		ItemPtrBasket::const_iterator it = s.begin();
		HWND curr;
		while (it != s.end())
		{
			Window &win = *(*it)->Ctl();
			win.SetFontHandle(_controlFont);

			curr = win.GetHandle();
			::SetParent(curr,_hwnd);
			_items.push_back(*it);
			it++;
		}	   
		AdjustControlLayout();
		RedrawWindow();
		LockWindowUpdate(NULL);    
	}

	void CompositeEditBox::ClearItems(bool redraw)
	{
		// first remove the focus from the control
		HWND parent_handle = GetParentHandle();
		ASSERT(parent_handle != 0);
		::SetFocus(parent_handle);

		ItemPtrBasket::iterator it = _items.begin();
		while (it != _items.end())
		{
			delete *it;
			it++;
		}
		_items.clear();
		RECT r;
		GetClientRect(r);
		OnSize(r.right,r.bottom);
		if (redraw)
			RedrawWindow();
	}

	void CompositeEditBox::OnSize(const int w, const int h)
	{
		_itemWidth = w;
		_displayHeight = h;
		const int count = _items.size();
		if (h == 0 || count == 0)
			return;
		AdjustControlLayout();
	}

	void CompositeEditBox::OnPaint()
	{
		if (!HasItems())
			return;
		PAINTSTRUCT ps;    
		HDC handle = BeginPaint(_hwnd,&ps);
		int top = 0;
		const int mid = middle(_itemWidth);
		HFONT hOldFont, hNewFont;
		hNewFont = _controlFont; 
		hOldFont = (HFONT) SelectObject(handle,hNewFont);

		SetBkMode(handle, TRANSPARENT);
		unsigned int count = _yPos + _ySize + 1;
		if (count > _items.size())
			count = _items.size();
		for (unsigned int i=_yPos; i < count; i++)
		{
			TextOut(handle,10,top + 4,_items[i]->Label().c_str(),
				_items[i]->Label().size());
			top += _itemHeight;
			MoveToEx(handle,10,top-3,NULL);
			LineTo(handle,mid,top-3);
		}
		SelectObject(handle,hOldFont);
		EndPaint(_hwnd,&ps);
	}

	void CompositeEditBox::OnScroll(int n)
	{
		if (_yPos + n < 0) 
			n = -_yPos;
		else if (_yPos + n  >= (int) _items.size() - _ySize)
			n = _items.size() - _yPos - _ySize;
		ScrollVert(-n*_itemHeight);
		_yPos += n;
		SetScrollVertPos(_yPos);
	}

	void CompositeEditBox::OnScrollPos(const int p)
	{
		OnScroll(p-_yPos);
	}

	void CompositeEditBox::OnScrollPage(const int n)
	{
		OnScroll(n*_ySize);
	}

	int CompositeEditBox::IndexOf(HWND handle)
	{
		ASSERT(_items.size() < 100);
		for (size_t i=0; i < _items.size(); i++)
			if (_items[i]->Ctl()->GetHandle() == handle)
				return i;
		return -1;  
	}

	void CompositeEditBox::OnFocus(HWND handle)
	{
		int index = IndexOf(handle);
		ASSERT(index != -1);
		if (index > _yPos+_ySize-1 || index < _yPos)
			OnScrollPos(index);
	}
	/* TODO (#2#): Improve user interface agility by allowing 
	for "AfterModify" event.  This will allow the immediate 
	update of "other" visual componetes -- e.g. focus item 
	in the tree, etc. */


}


